package com.xuyaxu.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 使用RedisTemplate实现redis分布式锁（正确的书写方式）
 * 测试之前需要在redis中给key kc:key:0830 设置库存数量为30
 * 使用 jMeter 进行并发测试，并发量为31，结果秒杀成功30，失败1个（提示秒杀光了）
 *
 * @author xuyaxu
 * @date 2022/9/1 20:55
 */
@RestController
@RequestMapping("redisTemplate-lock")
public class RedisTemplateLockController {

    /**
     * 锁的key
     */
    private static final String LOCK_KEY = "redis:lock:0830";

    /**
     * 库存数量的key
     */
    private static final String INVENTORY_QUANTITY_KEY = "kc:key:0830";

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @GetMapping("spike")
    public String spike() {
        // 请求标识
        String requestId = UUID.randomUUID().toString();
        // 锁的存活时间（单位：毫秒）
        int expireTime = 60;

        // 获取锁
        boolean flag = tryGetDistributedLock(LOCK_KEY, requestId, expireTime);
        while (!flag) {
            System.out.println(requestId + " 获锁失败");
            flag = tryGetDistributedLock(LOCK_KEY, requestId, expireTime);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 模拟业务处理耗时
        try {
            Thread.sleep(2000);
            // 查询库存剩余数量
            int kcNum = (int) redisTemplate.opsForValue().get(INVENTORY_QUANTITY_KEY);
            if (kcNum < 1) {
                return "商品太火爆了，已秒杀光了";
            }
            redisTemplate.opsForValue().decrement(INVENTORY_QUANTITY_KEY);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放锁
            boolean result = releaseDistributedLock(LOCK_KEY, requestId);
            if (!result) {
                System.out.println("释放锁失败，这不是我的锁");
            } else {
                System.out.println("释放锁成功");
            }
        }
        return "秒杀成功了";
    }


    /**
     * 尝试获取分布式锁
     *
     * @param lockKey    锁
     * @param requestId  请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public boolean tryGetDistributedLock(String lockKey, String requestId, int expireTime) {
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTime, TimeUnit.SECONDS);
        if (aBoolean != null && aBoolean) {
            return true;
        }
        return false;
    }

    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 释放分布式锁
     *
     * @param lockKey   锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public boolean releaseDistributedLock(String lockKey, String requestId) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Long result = redisTemplate.execute(redisScript, Arrays.asList(lockKey), requestId);
        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

}
